


/*-------------------------------------------------------------------------
 *
 * pqformat.c
 *		用于格式化和解析前端/后端消息的例程
 *
 * 发出的消息是构建在一个 StringInfo 缓冲区中（该缓冲区可以扩展）
 * 然后通过单次调用 pq_putmessage 发送。此模块提供了生成有效消息
 * 所需的数据格式化/转换例程。特别注意“原始数据”和“文本”之间的区别；
 * 原始数据是消息协议字符和不受字符集转换影响的二进制值，而文本则
 * 通过字符编码规则进行转换。
 *
 * 传入的消息类似地被读取到 StringInfo 缓冲区，通过 pq_getmessage，然后
 * 使用本模块中的例程解析和转换。
 *
 * 这些相同的例程支持外部二进制格式的读写（typsend/typreceive 例程）。
 * 各个数据类型的转换例程完全相同，仅初始化和完成是不同的。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *	src/backend/libpq/pqformat.c
 *
 *-------------------------------------------------------------------------
 */
/*
 * 接口例程
 * 消息组装和输出：
 *		pq_beginmessage - 初始化 StringInfo 缓冲区
 *		pq_sendbyte		- 将原始字节附加到 StringInfo 缓冲区
 *		pq_sendint		- 将二进制整数附加到 StringInfo 缓冲区
 *		pq_sendint64	- 将二进制 8 字节整数附加到 StringInfo 缓冲区
 *		pq_sendfloat4	- 将 float4 附加到 StringInfo 缓冲区
 *		pq_sendfloat8	- 将 float8 附加到 StringInfo 缓冲区
 *		pq_sendbytes	- 将原始数据附加到 StringInfo 缓冲区
 *		pq_sendcountedtext - 附加计数文本字符串（带字符集转换）
 *		pq_sendtext		- 附加文本字符串（带转换）
 *		pq_sendstring	- 附加以空字符结束的文本字符串（带转换）
 *		pq_send_ascii_string - 附加以空字符结束的文本字符串（不带转换）
 *		pq_endmessage	- 将完成的消息发送至前端
 * 注意：也可以使用常规 StringInfo 例程将数据附加到 StringInfo 缓冲区，但这不被推荐，因为可能不会进行必要的字符集转换。
 *
 * typsend 支持（构造一个包含外部二进制数据的 bytea 值）：
 *		pq_begintypsend - 初始化 StringInfo 缓冲区
 *		pq_endtypsend	- 将完成的字符串作为 "bytea*" 返回
 *
 * 特殊情况消息输出：
 *		pq_puttextmessage - 一步生成一个字符集转换的消息
 *		pq_putemptymessage - 方便的例程，用于发送空主体的消息
 *
 * 输入后的消息解析：
 *		pq_getmsgbyte	- 从消息缓冲区获取原始字节
 *		pq_getmsgint	- 从消息缓冲区获取二进制整数
 *		pq_getmsgint64	- 从消息缓冲区获取二进制 8 字节整数
 *		pq_getmsgfloat4 - 从消息缓冲区获取 float4
 *		pq_getmsgfloat8 - 从消息缓冲区获取 float8
 *		pq_getmsgbytes	- 从消息缓冲区获取原始数据
 *		pq_copymsgbytes - 从消息缓冲区复制原始数据
 *		pq_getmsgtext	- 获取计数文本字符串（带转换）
 *		pq_getmsgstring - 获取以空字符结束的文本字符串（带转换）
 *		pq_getmsgrawstring - 获取以空字符结束的文本字符串 - 无转换
 *		pq_getmsgend	- 验证消息已完全消费
 */

#include "postgres.h"

#include <sys/param.h>

#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
#include "port/pg_bswap.h"


/* --------------------------------
 *		pq_beginmessage		- 初始化以发送消息
 * --------------------------------
 */
void pq_beginmessage(StringInfo fc_buf, char fc_msgtype)
{
	initStringInfo(fc_buf);

	/*
	 * 我们将消息类型存储到缓冲区的游标字段中，期望
	 * pq_sendXXX 例程不会触及它。我们也可以选择将其作为
	 * 缓冲区内容的第一个字节，但这似乎更简单。
	 */
	fc_buf->cursor = fc_msgtype;
}

/* --------------------------------

 *		pq_beginmessage_reuse - 初始化以发送消息，重用缓冲区
 *
 * 这要求缓冲区在足够长生命周期的内存上下文中分配。
 * --------------------------------
 */
void pq_beginmessage_reuse(StringInfo fc_buf, char fc_msgtype)
{
	resetStringInfo(fc_buf);

	/*
	 * 我们将消息类型存储到缓冲区的游标字段中，期望
	 * pq_sendXXX 例程不会触及它。我们也可以选择将其作为
	 * 缓冲区内容的第一个字节，但这似乎更简单。
	 */
	fc_buf->cursor = fc_msgtype;
}

/* --------------------------------
 *		pq_sendbytes	- 将原始数据附加到 StringInfo 缓冲区
 * --------------------------------
 */
void pq_sendbytes(StringInfo fc_buf, const char *fc_data, int fc_datalen)
{
	/* 使用保持尾随空字节的变体，以防万一 */
	appendBinaryStringInfo(fc_buf, fc_data, fc_datalen);
}

/* --------------------------------
 *		pq_sendcountedtext - 附加一个计数字符串（带字符集转换）
 *
 * 本例程发送到前端的数据是一个 4 字节的计数字段
 * 后跟字符串。计数是否包括自身，取决于
 * countincludesself 标志（3.0 之前的协议要求其包括自身）。
 * 传递的文本字符串不需要以空字符结尾，并且发送
 * 到前端的数据也不需要。
 * --------------------------------
 */
void pq_sendcountedtext(StringInfo fc_buf, const char *fc_str, int fc_slen,
				   bool fc_countincludesself)
{
	int			fc_extra = fc_countincludesself ? 4 : 0;
	char	   *fc_p;

	fc_p = pg_server_to_client(fc_str, fc_slen);
	if (fc_p != fc_str)				/* 实际转换已经完成？ */
	{
		fc_slen = strlen(fc_p);
		pq_sendint32(fc_buf, fc_slen + fc_extra);
		appendBinaryStringInfoNT(fc_buf, fc_p, fc_slen);
		pfree(fc_p);
	}
	else
	{
		pq_sendint32(fc_buf, fc_slen + fc_extra);
		appendBinaryStringInfoNT(fc_buf, fc_str, fc_slen);
	}
}

/* --------------------------------
 *		pq_sendtext		- 附加一个文本字符串（带转换）
 *
 * 传递的文本字符串不需要以空字符结尾，并且发送
 * 到前端的数据也不需要。请注意，这对于直接的前端传输
 * 并不是特别有用，因为前端无法确定字符串的长度。
 * 但它对于二进制格式转换是有用的。
 * --------------------------------
 */
void pq_sendtext(StringInfo fc_buf, const char *fc_str, int fc_slen)
{
	char	   *fc_p;

	fc_p = pg_server_to_client(fc_str, fc_slen);
	if (fc_p != fc_str)				/* 实际转换已经完成？ */
	{
		fc_slen = strlen(fc_p);
		appendBinaryStringInfo(fc_buf, fc_p, fc_slen);
		pfree(fc_p);
	}
	else
		appendBinaryStringInfo(fc_buf, fc_str, fc_slen);
}

/* --------------------------------
 *		pq_sendstring	- 附加一个以空字符结尾的文本字符串（带转换）
 *
 * 注意：传递的文本字符串必须以空字符结尾，发送到
 * 前端的数据也是如此。
 * --------------------------------
 */
void pq_sendstring(StringInfo fc_buf, const char *fc_str)
{
	int			fc_slen = strlen(fc_str);
	char	   *fc_p;

	fc_p = pg_server_to_client(fc_str, fc_slen);
	if (fc_p != fc_str)				/* 实际转换已经完成？ */
	{
		fc_slen = strlen(fc_p);
		appendBinaryStringInfoNT(fc_buf, fc_p, fc_slen + 1);
		pfree(fc_p);
	}
	else
		appendBinaryStringInfoNT(fc_buf, fc_str, fc_slen + 1);
}

/* --------------------------------
 *		pq_send_ascii_string	- 附加一个以空字符结尾的文本字符串（不带转换）
 *
 * 此函数故意绕过编码转换，而是静默地将任何非 7 位 ASCII 字符替换为问号。
 * 仅在我们在正常本地化和编码转换时遇到向客户端发送错误消息的问题时使用。
 * 调用方应已采取措施确保字符串仅为 ASCII；
 * 这里的额外工作只是为了确保我们不向客户端发送错误编码的字符串
 * （客户端可能会或可能不会对此具有鲁棒性）。
 *
 * 注意：传递的文本字符串必须以空字符结尾，发送到
 * 前端的数据也是如此。
 * --------------------------------
 */
void pq_send_ascii_string(StringInfo fc_buf, const char *fc_str)
{
	while (*fc_str)
	{
		char		fc_ch = *fc_str++;

		if (IS_HIGHBIT_SET(fc_ch))
			fc_ch = '?';
		appendStringInfoCharMacro(fc_buf, fc_ch);
	}
	appendStringInfoChar(fc_buf, '\0');
}

/* --------------------------------
 *		pq_sendfloat4	- 将 float4 附加到 StringInfo 缓冲区
 *
 * 此例程的要点是本地化对 float4 外部二进制
 * 表示的知识，它是多个数据类型的一个组成部分。
 *
 * 我们目前假设 float4 应该以与 int4 相同的方式进行字节交换。
 * 这个规则并不完美，但它使我们在大多数使用 IEEE 浮点的架构中具有可移植性。
 * --------------------------------
 */
void pq_sendfloat4(StringInfo fc_buf, float4 fc_f)
{
	union
	{
		float4		f;
		uint32		i;
	}			fc_swap;

	fc_swap.f = fc_f;
	pq_sendint32(fc_buf, fc_swap.i);
}

/* --------------------------------
 *		pq_sendfloat8	- 将一个 float8 附加到 StringInfo 缓冲区
 *
 * 此例程的目的在于局部化 float8 的外部二进制表示的知识，
 * 这是一些数据类型的组成部分。
 *
 * 我们目前假设 float8 应该以与 int8 相同的方式进行字节交换。
 * 这个规则并不完美，但它为大多数使用 IEEE 浮点架构提供了可移植性。
 * --------------------------------
 */
void pq_sendfloat8(StringInfo fc_buf, float8 fc_f)
{
	union
	{
		float8		f;
		int64		i;
	}			fc_swap;

	fc_swap.f = fc_f;
	pq_sendint64(fc_buf, fc_swap.i);
}

/* --------------------------------
 *		pq_endmessage	- 将完成的消息发送到前端
 *
 * 数据缓冲区被 pfree()，但如果 StringInfo 是通过
 * makeStringInfo 分配的，那么调用者仍然必须 pfree 它。
 * --------------------------------
 */
void pq_endmessage(StringInfo fc_buf)
{
	/* msgtype 保存在光标字段中 */
	(void) pq_putmessage(fc_buf->cursor, fc_buf->data, fc_buf->len);
	/* 无需抱怨任何失败，因为 pqcomm.c 已经做了 */
	pfree(fc_buf->data);
	fc_buf->data = NULL;
}

/* --------------------------------
 *		pq_endmessage_reuse	- 将完成的消息发送到前端
 *
 * 数据缓冲区 *不* 被释放，允许使用 pq_beginmessage_reuse 重用缓冲区。
 --------------------------------
 */

void pq_endmessage_reuse(StringInfo fc_buf)
{
	/* msgtype 保存在光标字段中 */
	(void) pq_putmessage(fc_buf->cursor, fc_buf->data, fc_buf->len);
}


/* --------------------------------
 *		pq_begintypsend		- 为构造 bytea 结果初始化
 * --------------------------------
 */
void pq_begintypsend(StringInfo fc_buf)
{
	initStringInfo(fc_buf);
	/* 为 bytea 长度字保留四个字节 */
	appendStringInfoCharMacro(fc_buf, '\0');
	appendStringInfoCharMacro(fc_buf, '\0');
	appendStringInfoCharMacro(fc_buf, '\0');
	appendStringInfoCharMacro(fc_buf, '\0');
}

/* --------------------------------
 *		pq_endtypsend	- 完成构造 bytea 结果
 *
 * 数据缓冲区作为 palloc'd bytea 值返回。 （我们期望
 * 因为它已经被 palloc，所以将自动适当地对齐。）
 * 我们假设 StringInfoData 只是调用者中的一个局部变量
 * 无需 pfree。
 * --------------------------------
 */
bytea * pq_endtypsend(StringInfo fc_buf)
{
	bytea	   *fc_result = (bytea *) fc_buf->data;

	/* 将正确的长度插入 bytea 长度字 */
	Assert(fc_buf->len >= VARHDRSZ);
	SET_VARSIZE(fc_result, fc_buf->len);

	return fc_result;
}


/* --------------------------------
 *		pq_puttextmessage - 生成一个字符集转换消息的一步操作
 *
 *		这和 pqcomm.c 例程 pq_putmessage 相同，除了
 *		消息主体是一个适用编码转换的以 null 结束的字符串。
 * --------------------------------
 */
void pq_puttextmessage(char fc_msgtype, const char *fc_str)
{
	int			fc_slen = strlen(fc_str);
	char	   *fc_p;

	fc_p = pg_server_to_client(fc_str, fc_slen);
	if (fc_p != fc_str)				/* 实际转换已经完成？ */
	{
		(void) pq_putmessage(fc_msgtype, fc_p, strlen(fc_p) + 1);
		pfree(fc_p);
		return;
	}
	(void) pq_putmessage(fc_msgtype, fc_str, fc_slen + 1);
}


/* --------------------------------
 *		pq_putemptymessage - 用于空主体消息的便利例程
 * --------------------------------
 */
void pq_putemptymessage(char fc_msgtype)
{
	(void) pq_putmessage(fc_msgtype, NULL, 0);
}


/* --------------------------------
 *		pq_getmsgbyte	- 从消息缓冲区获取一个原始字节
 * --------------------------------
 */
int pq_getmsgbyte(StringInfo fc_msg)
{
	if (fc_msg->cursor >= fc_msg->len)
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("no data left in message")));
	return (unsigned char) fc_msg->data[fc_msg->cursor++];
}

/* --------------------------------
 *		pq_getmsgint	- 从消息缓冲区获取一个二进制整数
 *
 *		值被视为无符号数。
 * --------------------------------
 */
unsigned int pq_getmsgint(StringInfo fc_msg, int fc_b)
{
	unsigned int fc_result;
	unsigned char fc_n8;
	uint16		fc_n16;
	uint32		fc_n32;

	switch (fc_b)
	{
		case 1:
			pq_copymsgbytes(fc_msg, (char *) &fc_n8, 1);
			fc_result = fc_n8;
			break;
		case 2:
			pq_copymsgbytes(fc_msg, (char *) &fc_n16, 2);
			fc_result = pg_ntoh16(fc_n16);
			break;
		case 4:
			pq_copymsgbytes(fc_msg, (char *) &fc_n32, 4);
			fc_result = pg_ntoh32(fc_n32);
			break;
		default:
			elog(ERROR, "unsupported integer size %d", fc_b);
			fc_result = 0;			/* 保持编译器安静 */
			break;
	}
	return fc_result;
}

/* --------------------------------
 *		pq_getmsgint64	- 从消息缓冲区获取一个二进制8字节整数
 *
 * 将此与pq_getmsgint合并是很诱人的，但我们必须让结果对于所有数据宽度都为int64 --- 这可能会在int64效率不高的机器上造成性能损失。
 * --------------------------------
 */
int64 pq_getmsgint64(StringInfo fc_msg)
{
	uint64		fc_n64;

	pq_copymsgbytes(fc_msg, (char *) &fc_n64, sizeof(fc_n64));

	return pg_ntoh64(fc_n64);
}

/* --------------------------------
 *		pq_getmsgfloat4 - 从消息缓冲区获取一个float4
 *
 * 请参见pq_sendfloat4的说明。
 * --------------------------------
 */
float4
pq_getmsgfloat4(StringInfo fc_msg)
{
	union
	{
		float4		f;
		uint32		i;
	}			fc_swap;

	fc_swap.i = pq_getmsgint(fc_msg, 4);
	return fc_swap.f;
}

/* --------------------------------
 *		pq_getmsgfloat8 - 从消息缓冲区获取一个float8
 *
 * 请参见pq_sendfloat8的说明。
 * --------------------------------
 */
float8 pq_getmsgfloat8(StringInfo fc_msg)
{
	union
	{
		float8		f;
		int64		i;
	}			fc_swap;

	fc_swap.i = pq_getmsgint64(fc_msg);
	return fc_swap.f;
}

/* --------------------------------
 *		pq_getmsgbytes	- 从消息缓冲区获取原始数据
 *
 *		直接返回指向消息缓冲区的指针；请注意，这可能没有任何特定的对齐。
 * --------------------------------
 */
const char * pq_getmsgbytes(StringInfo fc_msg, int fc_datalen)
{
	const char *fc_result;

	if (fc_datalen < 0 || fc_datalen > (fc_msg->len - fc_msg->cursor))
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("insufficient data left in message")));
	fc_result = &fc_msg->data[fc_msg->cursor];
	fc_msg->cursor += fc_datalen;
	return fc_result;
}

/* --------------------------------
 *		pq_copymsgbytes - 从消息缓冲区复制原始数据
 *
 *		与上述相同，除了数据被复制到调用者的缓冲区。
 * --------------------------------
 */
void pq_copymsgbytes(StringInfo fc_msg, char *fc_buf, int fc_datalen)
{
	if (fc_datalen < 0 || fc_datalen > (fc_msg->len - fc_msg->cursor))
#ifdef FDD //cppcheck
	{
		//cppcheck
#endif
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("insufficient data left in message")));
#ifdef FDD //cppcheck
		return;
	}
#endif
	memcpy(fc_buf, &fc_msg->data[fc_msg->cursor], fc_datalen);
	fc_msg->cursor += fc_datalen;
}

/* --------------------------------
 *		pq_getmsgtext	- 获取一个计数的文本字符串（带转换）
 *
 *		始终返回指向新分配结果的指针。
 *		结果有一个尾随的空字符，并且我们在*nbytes中返回其字符串长度。
 * --------------------------------
 */
char * pq_getmsgtext(StringInfo fc_msg, int fc_rawbytes, int *fc_nbytes)
{
	char	   *fc_str;
	char	   *fc_p;

	if (fc_rawbytes < 0 || fc_rawbytes > (fc_msg->len - fc_msg->cursor))
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("insufficient data left in message")));
	fc_str = &fc_msg->data[fc_msg->cursor];
	fc_msg->cursor += fc_rawbytes;

	fc_p = pg_client_to_server(fc_str, fc_rawbytes);
	if (fc_p != fc_str)				/* 实际转换已经完成？ */
		*fc_nbytes = strlen(fc_p);
	else
	{
		fc_p = (char *) palloc(fc_rawbytes + 1);
		memcpy(fc_p, fc_str, fc_rawbytes);
		fc_p[fc_rawbytes] = '\0';
		*fc_nbytes = fc_rawbytes;
	}
	return fc_p;
}

/* --------------------------------
 *		pq_getmsgstring - 获取一个以空字符结束的文本字符串（带转换）
 *
 *		可能直接返回指向消息缓冲区的指针，或指向分配的转换结果的指针。
 * --------------------------------
 */
const char * pq_getmsgstring(StringInfo fc_msg)
{
	char	   *fc_str;
	int			fc_slen;

	fc_str = &fc_msg->data[fc_msg->cursor];

	/*
	 * 在这里使用strlen()是安全的，因为StringInfo保证有一个尾随的空字节。
	 * 但是检查我们在消息中找到了一个空字符。
	 */
	fc_slen = strlen(fc_str);
	if (fc_msg->cursor + fc_slen >= fc_msg->len)
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("invalid string in message")));
	fc_msg->cursor += fc_slen + 1;

	return pg_client_to_server(fc_str, fc_slen);
}

/* --------------------------------
 *		pq_getmsgrawstring - 获取一个以空字符结束的文本字符串 - 不转换
 *
 *		直接返回指向消息缓冲区的指针。
 * --------------------------------
 */
const char * pq_getmsgrawstring(StringInfo fc_msg)
{
	char	   *fc_str;
	int			fc_slen;

	fc_str = &fc_msg->data[fc_msg->cursor];

	/*
	 * 在这里使用strlen()是安全的，因为StringInfo保证有一个尾随的空字节。
	 * 但是检查我们在消息中找到了一个空字符。
	 */
	fc_slen = strlen(fc_str);
	if (fc_msg->cursor + fc_slen >= fc_msg->len)
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("invalid string in message")));
	fc_msg->cursor += fc_slen + 1;

	return fc_str;
}

/* --------------------------------
 *		pq_getmsgend	- 验证消息是否完全消费
 * --------------------------------
 */
void pq_getmsgend(StringInfo fc_msg)
{
	if (fc_msg->cursor != fc_msg->len)
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("invalid message format")));
}
